import {Injectable} from '@angular/core';
import {Hero} from "./hero";
import {HEROES} from "./mock-heroes";
import {catchError, Head, Observable, of, tap} from "rxjs";
import {MessageService} from "./message.service";
import {HttpClient, HttpHeaders} from "@angular/common/http";

@Injectable({
  providedIn: 'root'
})
export class HeroService {

  private heroesUrl = 'api/heroes';  // URL to web api

  /**
   *
   */
  httpOptions = {
    headers: new HttpHeaders({'Content-Type': 'application/json'})
  };

  constructor(
    private http: HttpClient,
    private messageService: MessageService) {
  }

  /**
   * 同步函数签名
   */
  getHeroes1(): Hero[] {
    return HEROES;
  }

  /**
   * 异步函数签名
   * Observable 可观察者
   * 使用 RxJS 的 of() 函数来模拟从服务器返回数据，of(HEROES) 会返回一个 Observable<Hero[]>
   * @return Observable<Hero[]>
   */
  // getHeroes(): Observable<Hero[]> {
  //   const heroes = of(HEROES);
  //   this.messageService.add('HeroService: fetched heroes');
  //   return heroes;
  // }

  /**
   * 通过 HttpClient 获取英雄
   * Observable 可观察者
   * HttpClient.get() 默认情况下把响应体当做无类型的 JSON 对象进行返回。 如果指定了可选的模板类型 <Hero[]>，就会给返回你一个类型化的对象
   * 服务器的数据 API 决定了 JSON 数据的具体形态。 英雄之旅的数据 API 会把英雄数据作为一个数组进行返回。
   * 要捕获错误，你就要使用 RxJS 的 catchError() 操作符来建立对 Observable 结果的处理管道（pipe）
   * catchError() 操作符会拦截失败的 Observable。 它把错误对象传给错误处理器，错误处理器会处理这个错误。
   * 使用 RxJS 的 tap() 操作符来实现，该操作符会查看 Observable 中的值，使用那些值做一些事情，并且把它们传出来。 这种 tap() 回调不会改变这些值本身
   * tap窥探 Observable 的数据流，并通过 log() 方法往页面底部发送一条消息
   */
  getHeroes(): Observable<Hero[]> {
    //let objectObservable = this.http.get(this.heroesUrl);

    return this.http
      // get请求
      .get<Hero[]>(this.heroesUrl)
      .pipe(
        tap(_ => this.log('fetched heroes')),
        //捕获错误
        catchError(this.handleError<Hero[]>('getHeroes', []))
      );
  }

  /**
   *
   * @param message
   * @private
   */
  private log(message: string) {
    this.messageService.add(`HeroService: ${message}`);
  }

  /**
   * 按参数操作数组中对象，支持路由模式
   *
   * @param id
   * @return Observable<Hero>
   */

  /**
   getHero(id: number): Observable<Hero> {
    // For now, assume that a hero with the specified `id` always exists.
    // Error handling will be added in the next step of the tutorial.
    const hero = HEROES.find(h => h.id === id)!;
    // 注意，反引号 ( ` ) 用于定义 JavaScript 的 模板字符串字面量，以便嵌入 id。
    this.messageService.add(`HeroService: fetched hero id=${id}`);
    return of(hero);
  }*/

  /**
   * 按参数操作数组中对象，Http请求
   * @param id Web API 都支持以 :baseURL/:id 的形式根据 id 进行获取
   */
  getHero(id: number): Observable<Hero> {

    const url = `${this.heroesUrl}/${id}`;
    return this.http
      .get<Hero>(url)
      .pipe(
        tap(_ => this.log(`fetched hero id=${id}`)),
        catchError(this.handleError<Hero>(`getHero id=${id}`))
      );
  }

  /**
   * 根据选择英雄修改数据
   * @param hero
   */
  updateHero(hero: Hero): Observable<any> {
    return this.http
      // 三个参数：URL\要修改的数据\选项
      .put(this.heroesUrl, hero, this.httpOptions)
      .pipe(
        tap(_ => this.log(`updated hero id=${hero.id}`)),
        catchError(this.handleError<any>('updateHero'))
      );
  }

  /**
   * 添加
   * @param hero
   */
  addHero(hero: Hero): Observable<Hero> {
    return this.http
      .post<Hero>(this.heroesUrl, hero, this.httpOptions)
      .pipe(
        tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
        catchError(this.handleError<Hero>('addHero'))
      );
  }

  /**
   * 删除
   * URL 就是英雄的资源 URL 加上要删除的英雄的 id
   * 不用像 put() 和 post() 中那样发送任何数据
   * @param id
   */
  deleteHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;

    return this.http
      .delete<Hero>(url, this.httpOptions)
      .pipe(
        tap(_ => this.log(`deleted hero id=${id}`)),
        catchError(this.handleError<Hero>('deleteHero'))
      );
  }

  /**
   * 根据名字搜索
   * 如果没有搜索词，该方法立即返回一个空数组。 剩下的部分和 getHeroes() 很像。 唯一的不同点是 URL，它包含了一个由搜索词组成的查询字符串
   * @param term
   */
  searchHeroes(term: string): Observable<Hero[]> {

    // 没有搜索词，该方法立即返回一个空数组
    if (!term.trim()) {
      return of([]);
    }
    return this.http
      // URL 包含了一个由搜索词组成的查询字符串
      .get<Hero[]>(`${this.heroesUrl}/?name=${term}`)
      .pipe(
        tap(x => x.length ?
          this.log(`found heroes matching "${term}"`) :
          this.log(`no heroes matching "${term}"`)),
        catchError(this.handleError<Hero[]>('searchHeroes', []))
      );
  }

  /**
   * 错误处理
   *
   * @param operation
   * @param result
   * @private
   */
  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(error);
      this.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    }
  }
}
